#include <arch.h>
#include <t8.h>
#include <bo_config.h>
#include "config.h"
#include "command.h"
#include "serial.h"
#include "time.h"
#include "main.h"
#include "util.h"
#include "irq.h"
#include "xmodem.h"
#include "ymodem.h"
#include "tat.h"
#include "crc16.h"
#include "usb_serial_download.h"
#include "KeypadTest.h"
#include "nandflash.h"
#include <i2c.h>

//#define TAT 0

//The following is used to update the whole Image by USB

volatile int iimageupdate_by_usb=0;
unsigned int verbose_level = 1;

extern int udownload_cmd(u32 *destAddress, u32 bufLen);
extern int UsbDownloadImageEx();
extern int md5_stream (char *start_address, int length);
void BootKernel(int arg0, int arg1);
void Download(u8* address);
void Jump(u32* address);
void JumptoTAT(void);
void ShowVersion(void);

void ResetTerminal(void);
void PrintSerialSpeed(serial_baud_t speed);
void ExecuteCommand(u8* commandline);
void hw_reset(void);
void ArmSys();
void tat_menu();

int g_bWrite = 1;

#ifdef TAT
unsigned char rts_signal;
int rts_try;
#endif

void ShowUsbPinState(void)
{
}

void Do_InitLcd();
void Display_Logo();
int bNormalBoot = 0;
void backlight_open();

void check_onkey()
{
    if(!bNormalBoot) {
        //printk("\n\n bNormalBoot = %x, GPLR0 = %x, GEDR0 = %x, jiffies = %d\n\n\n",
        //    bNormalBoot, GPLR0 & 3, GEDR0 & 3, jiffies);

        if (jiffies < 150) {//1-3s
            //shutdown when usb cable not pluged AND onkey not pressed
            printk("should shutdown!\n");
        }
        else {
//            printk("onkey pressed for more than 1s, start now\n");
//            backlight_open();
            bNormalBoot = 1;
        }
    }
}


#define KEY_TIMEOUT                                        0x54
#define KEY_AUTOLOAD                                    KEY_TIMEOUT

#define KEY_USB_DOWNLOAD_MODE        0x40   // VOLUME_UP
#define KEY_USB_DOWNLOAD_MODE_1      0x41   // VOLUME_DOWN
#define KEY_BOOTLOADER_CONSOLE       0x42

static void EnterAutoloadMode()
{
    /* Jump to kernel */
    SerialOutputString("\nAuto Boot Entered\n");
    if (Do_ReadKernel((unsigned char*)KERNEL_RAM_START, KERNEL_OFFSET, KERNEL_LENGTH)!= -1) {
        cli();
        BootKernel(BOOT_ARG0, BOOT_ARG1);
    }
    else {
        printf("read storage error\n");
    }
    return;
}

/*
; PARAMETER PASSING:
;
;       INPUTS:
;          baseAddr = starting address where the memory size tests are to begin
;          increment = memory increment size (Typically, 0x100000 for 1Mb, or 0x400 for 1Kb)
;       RETURN:
;          Memory size (in bytes) - also saved at address in r0
;
; NOTES:
;
;   The program writes a value to memory at (increment size) increments and checks to make sure
;   that the base address has not been modified (due to memory wrap) to calculate the
;   size of the memory.
*/
unsigned long testMemorySize(const unsigned long baseAddr, unsigned long increment)
{
    unsigned long addr = baseAddr;
    *(unsigned long *)(addr)  = baseAddr;

    unsigned long cnt = 0;
    while (1) {

        addr += increment;

        *(unsigned long *)(addr)  = addr;

        if (*(unsigned long *)(baseAddr) != baseAddr )
            goto Exit;

        cnt++;

        if (*(unsigned long *)(addr)  != addr)
            goto Exit;

    }

Exit:
    if (cnt != 0) {//was any memory detected?
        cnt++;
    }
    return (cnt * increment);

}

void EnterBootloaderConsole(void);

void backlight_on();
int watchdog_dump(char * param);
void  t8_main(void)
{
    backlight_on();

    watchdog_dump(0);

    // NF_Init(); // Already init in start_nl.S

//    unsigned int size  = testMemorySize(/*0x80000000*/0xa0000000, 0x400);
//    printf("The memory size is ( %d )\n",size);

    printk("Elastos loader. Compling time: %s %s\n", __DATE__, __TIME__);

#if 0
    unsigned char commandline[128] = {0,};
    if (SerialInputBlock_msec((char*)commandline,  1, 10)) {
        // printf("Armsys...\n");
        ArmSys();
    }
#endif

    // Initialize LCD and display LOGO.
    Do_InitLcd();
    Display_Logo();

    int key = 0;
    key = wls_keypad_getkey();//Init Already in board.cpp
    printk("!!!key=0x%x,Will test UsbInitilize", key);

    switch(key){
        case KEY_USB_DOWNLOAD_MODE:
        case KEY_USB_DOWNLOAD_MODE_1:
                if (key != 0x54/* 'T' */) {//Any key to download os image
                    printk("Get key=%x\n", key);
                    cli();
                    g_bWrite = 0;
                    if (UsbDownloadImageEx()) {
                        g_bWrite = 1;
                        BootKernel(BOOT_ARG0, BOOT_ARG1);
                    }
                    sti();
                }
                break;

        case KEY_BOOTLOADER_CONSOLE:
                EnterBootloaderConsole();
                break;

        default:
                EnterAutoloadMode();
                break;
    }

}

int watchdog_dump(char * param)
{
    return 0;
}

int _Do_Test_Poweroff(char *param)
{
    return 0;
}

int Do_LoadTAT(unsigned char * addr);
int _Do_Test_PowerMode(char * param)
{
//    SerialOutputString("\nAuto Boot Entered\n");
//    if (Do_LoadTAT((unsigned char *)KERNEL_RAM_START)!= -1) {
//        cli();
//        Jump((u32* )KERNEL_RAM_START);
//    }
//    else {
//        printf("read storage error\n");
//    }
    return 0;
}


unsigned long  mDOC_GetTIM( unsigned char *pAddress, unsigned long timByteCount );

void Port_Init(void)
{
    //CAUTION:Follow the configuration order for setting the ports.
    // 1) setting value(GPnDAT)
    // 2) setting control register  (GPnCON)
    // 3) configure pull-up resistor(GPnUP)

    //32bit data bus configuration
    //*** PORT A GROUP
    //Ports  : GPA22 GPA21   GPA20 GPA19 GPA18 GPA17 GPA16 GPA15 GPA14 GPA13 GPA12
    //Signal : nFCE BT_RESET NOUSE NOUSE NOUSE NOUSE NOUSE NOUSE NOUSE NOUSE NOUSE
    //Binary :  1     1      1  ,   1      1     1    1   ,  1     1     1     1
    //
    //Ports  : GPA11   GPA10  GPA9   GPA8   GPA7   GPA6   GPA5   GPA4   GPA3   GPA2   GPA1  GPA0
    //Signal : NOUSE   NOUSE ADDR24  NOUSE RST_BB  NOUSE  NOUSE ADDR19 ADDR18 ADDR17 ADDR16 ADDR0
    //Binary :  1       1      1      1 ,    1       1      1      1   ,  1       1     1      1
    rGPACON = 0x7fffff;

    //**** PORT B GROUP
    //Ports  : GPB10     GPB9      GPB8      GPB7      GPB6      GPB5      GPB4
    //Signal : Keypad_C5 Keypad_C4 Keypad_C3 Keypad_C2 Keypad_C1 Keypad_C0 CAM_STANDBY
    //Setting: OUTPUT    OUTPUT     OUTPUT    OUTPUT   OUTPUT     OUTPUT     OUTPUT
    //Binary :   00,       00      00  ,      00        00  ,      00       01
    //
    //Ports  : GPB3       GPB2      GPB1       GPB0
    //Signal : USBPOW_EN  BB_VT1    IRQ1_TO_BB CAM03_STANDBY
    //Setting: OUTPUT     OUTPUT?   OUTPUT     OUTPUT
    //Binary :   01        01   ,     01        01
    // rGPBCON = 0x000155;
    rGPBCON = 0x155555; // Keypad_Cx: OUTPUT
    rGPBUP = 0x7f7;

    //*** PORT C GROUP
    //Ports  : GPC15 GPC14 GPC13 GPC12 GPC11 GPC10       GPC9       GPC8
    //Signal : VD7   VD6   VD5   VD4   VD3   GPS_VCTL2   GPS_VCTL1  GPS_BOOTSEL
    //Binary :  10   10  , 10    10  , 10    01  ,       01         01  ,
    //
    //Ports  : GPC7        GPC6         GPC5         GPC4 GPC3  GPC2  GPC1 GPC0
    //Signal : GPS_Nsreset FLASH_STROBE FLASH_PWR_EN VDEN VSYNC HSYNC VCLK LCD_RESET
    //Binary : 01             01  ,       01         10,  10     10 , 10   01
    rGPCCON = 0xaa9556a9;
    //rGPCUP  = 0xffff;     // The pull down function is disabled GPC[15:0]

    //*** PORT D GROUP
    //Ports  : GPD15 GPD14 GPD13 GPD12 GPD11 GPD10    GPD9   GPD8
    //Signal : VD23  VD22  VD21  VD20  VD19  LCD_SD  SPI_SDA GPS_ON_OFF
    //Binary : 10    10  , 10    10  , 10    10  ,   11       01
    //
    //Ports  : GPD7 GPD6 GPD5 GPD4 GPD3 GPD2 GPD1     GPD0
    //Signal : VD15 VD14 VD13 VD12 VD11 VD10 SPI_SCL  SPI_CS
    //Binary : 10   10 , 10   10 , 10   10 ,  11      11
    rGPDCON = 0xaaadaaaf;
//    rGPDCON = 0xaa94aaa5;

    //*** PORT E GROUP
    //Ports  : GPE15  GPE14 GPE13         GPE12   GPE11     GPE10   GPE9    GPE8     GPE7  GPE6  GPE5   GPE4
    //Signal : IICSDA IICSCL PDP0_PU_CTRL USB_SW LED_DEBUG SDDATA3 SDDATA2 SDDATA1 SDDATA0 SDCMD SDCLK I2SSDO
    //Binary :  10     10  ,  01          01  ,    01       10   ,  10      10   ,   10    10  , 10     10  ,
    //-------------------------------------------------------------------------------------------------------
    //Ports  :  GPE3   GPE2  GPE1    GPE0
    //Signal : I2SSDI CDCLK I2SSCLK I2SLRCK
    //Binary :  10     10  ,  10      10
    rGPECON = 0xa56aaaaa;
    rGPEUP = 0xffff;


    //*** PORT F GROUP
    //Ports  : GPF7         GPF6   GPF5         GPF4      GPF3      GPF2    GPF1    GPF0
    //Signal : IRQ2_FROM_VT NOUSE HEADSET_DET   IRQ_GPS   BT_WAKEUP IRQ1_BB NOUSE   IRQ_DA9030
    //Setting: EINT7              EINT5         EINT4     EINT3     EINT2           EINT0
    //Binary :  10          00 ,   10            10  ,     10       10  ,   00      10
    rGPFCON = 0x8aa2;
    rGPFUP |= 0x20;


    //*** PORT G GROUP
    // GPG[15:13] must be selected as Input in NAND boot mode.
    //Ports  : GPG15 GPG14 GPG13 GPG12          GPG11    GPG10    GPG9     GPG8     GPG7    GPG6
    //Signal : GPG15 GPG14 GPG13 PDN0_PD_CTRL   ONOFF_BB NCTS1_TD NRTS1_TD USB_SW  ID_USB   ETH_IRQ
    //Setting: Input Input Input Output         Output   NCTS1    NRTS1     Output   EINT15
    //Binary :   00   00 ,  00    01  ,         01       11    ,   11       01   ,    10     00
    //-----------------------------------------------------------------------------------------
    //Ports  :    GPG5     GPG4      GPG3    GPG2       GPG1        GPG0
    //Signal : HOOK_DET  Keypad_R4 Keypad_R3 Keypad_R2 Keypad_R1 Keypad_R0
    //Setting:  EINT13    EINT12     EINT11   EINT10     EINT9     EINT8
    //Binary :   00/10      10   ,     10      10  ,       10        10
    rGPGCON = 0x017d82aa;
    rGPGUP |= 0x63f;


    //*** PORT H GROUP
    //Ports  :  GPH10          GPH9      GPH8   GPH7     GPH6     GPH5    GPH4    GPH3    GPH2    GPH1  GPH0
    //Signal : CLK_UART_GPS   CODECCLK  BB_VT2 RXD2_GPS TXD2_GPS RXD1_TD TXD1_TD RXD0_BT TXD0_BT nRTS0_BT nCTS0_BT
    //Binary :   10   ,         10       00(?) , 10       10  ,    10      10 ,   10      10 ,     10    10
    rGPHCON = 0x28aaaa;
    rGPHUP  = 0x7ff;    // The pull down function is disabled GPH[10:0]

    //PORT J GROUP
    //Ports    :  GPJ12    GPJ11     GPJ10      GPJ9  GPJ8      GPJ7    GPJ6  GPJ5    GPJ4  GPJ3  GPJ2  GPJ1  GPJ0
    //Signal : CAMRESET CAMCLKOUT CAMHREF CAMVS CAMPCLKIN CAMD7 CAMD6 CAMD5 CAMD4 CAMD3 CAMD2 CAMD1 CAMD0
    //Setting: Out      Out      CAMHREF CAMVS CAMPCLKIN CAMD7 CAMD6 CAMD5 CAMD4 CAMD3 CAMD2 CAMD1 CAMD0
    //Binary : 10        10        10      10    10        10    10    10    10    10    10    10    10
    //PU_OFF : 1        0          1          1     1         1     1     1        1      1     1     1     1
    //---------------------------------------------------------------------------------------
    rGPJCON = 0x2aaaaaa;
    //rGPJUP    = ((0<<12)|(1<<11));

    //External interrupt will be falling edge triggered.
    rEXTINT0 = 0x22222222;    // EINT[7:0]
    rEXTINT1 = 0x22222222;    // EINT[15:8]
    rEXTINT2 = 0x22222222;    // EINT[23:16]
}


int __cdecl c_main()
{
    Port_Init(); // Pin configuration

    TimerInit();

    SerialInit(baud_115200);

    InitI2c();
    I2cWriteByte(0x92, 0x08, 0xe8); // Close watch dog of PMU
    I2cWriteByte(0x92, 0x98, 0x31); // Close BT Power (BT connected with debug UART)
    I2cWriteByte(0x92, 0x28, 0xcc); // Start charging, 800mAh, 4.2V

    Xo_Main();

    t8_main();

    return 0;
}

void de_mem_test();

void ShowVersion()
{
    printk( "\n\tElastos2.0 Bootloader\n\tVersion:%s, Time Stamp:%s\n", BOOTLOADVERSION, COMPILE_TIME_STAMP );
}

/*
  Jump to the pre-copied kernel in the RAM
*/
typedef struct BootInfo {
    unsigned int LoadAddress;
    unsigned int ImageBase;
    unsigned int ImageSize;
    unsigned int MemorySize;
    unsigned int WriteMagic;
    unsigned int Reserved1;
    unsigned int Reserved2;
    unsigned int Reserved3;
} BootInfo;

typedef struct PackageInfo {
    unsigned int    uReserved;
    void            *modules;
    unsigned int    uNumberOfModules;
    unsigned int    uPackageSize;
    char            *szCommandLine;
} PackageInfo;

void BootKernel(int arg0, int arg1)
{
    BootInfo *pLoadInfo = (BootInfo *)RAM_START;
    int *pEntry;
    void (*theKernel)(int zero, int arch);
    // find kernel entry
    pEntry = (int*)(KERNEL_RAM_START + 0x3c);

    if ((*(int *)(*pEntry + KERNEL_RAM_START)) != 0x00004550) {
        printk("Kernel is not PE format!\n");
        return;
    }
    pEntry = (int*)(KERNEL_RAM_START + (*pEntry) + 0x28);
    theKernel = (void*)((*pEntry) + KERNEL_RAM_START);

    jiffies = 1000;
    check_onkey();

    // set kernel arguments
    if (!arg1) {
        arg1 = (int)pLoadInfo;
        pLoadInfo->WriteMagic = 0;
        pLoadInfo->MemorySize = 0x4000000;
        pLoadInfo->LoadAddress = KERNEL_RAM_START;
        pLoadInfo->ImageBase = 0xc0020000;
        pEntry = (int*)(KERNEL_RAM_START + 0x8);
        pLoadInfo->ImageSize = ((PackageInfo *)(*pEntry + KERNEL_RAM_START))\
                             ->uPackageSize;
    }
/*  printk("loadinfo(%08x):WriteMagic%08x\n"
           "MemorySize%08x\n"
           "LoadAddress%08x\n"
           "ImageBase%08x\n"
           "ImageSize%08x\n",
        pLoadInfo,
        pLoadInfo->WriteMagic,
        pLoadInfo->MemorySize,
        pLoadInfo->LoadAddress,
        pLoadInfo->ImageBase,
        pLoadInfo->ImageSize);*/

    printk("jump to kernel\n");
    cli();
    theKernel(arg0,arg1);
    sti();

    printk("Hey, the kernel returned! This should not happen.\r");
}

void Jump(u32* address)
{
    void (*Func)(void)= (void(*)(void))address;

    (*Func)();

    /* To return to this point, code should save every registes used,
     * lr, etc. and return to the lr and should never modify the SP
     */
}

static void WatchdogReset()
{
    printk("WatchdogReset\n");
    rWTCON = 0x8021;
    rWTDAT = 0x8000;
    while (1);
}

void hw_reset(void)
{
    int start_time;
    start_time = TimerGetTime();

    printk("Auto reset...\n");
    // while((TimerGetTime()-start_time) < 2);
    WatchdogReset();
}

// Enter bootloader
void Boot()
{
    Jump((u32*)KERNEL_RAM_START);
}



void ArmSys()
{
    unsigned long packsize,flashoffset;
    unsigned char * buffer = (unsigned char *)KERNEL_RAM_START;
    long IsWriteFlash;
    int read = 0;

    // NOTE: If the battery voltage is low, please remind users that
    // it's dangerous when updating loader!!!
    // When uploading, there's no power suddenley, the loader maybe destroyed.
    // And we can only use JTAG to burn the loader again.
    //
    // TODO: Add battery check code here
    //

    while (1) {
        // Send query signal,ask for file length
        SerialOutputRawByte('?');
        flashoffset = SerialInputDWordWithTimeout();
        if (flashoffset != -1) {
            break;
        }
    }

    //  Get file length nowait
    // flashoffset = SerialInputDWordWithTimeout();
    packsize = SerialInputDWordWithTimeout();
    IsWriteFlash = packsize & 0x80000000;
    packsize = packsize & ~0x80000000;
    //printf("get info %x,%x\n",flashoffset,packsize);
    if (packsize) {    // get image from armsys
        // end ready signal
        SerialOutputRawByte('<');

        // receive the file data
        read = SerialInputBlock_msec((char*)buffer, packsize, 0);

        SerialOutputRawByte('>');

//        printf("get info %x,%x\n",flashoffset,packsize);
//        printf("have got %x bytes, packsize=%x\n",read, packsize);
//        printf("%x\n", *(unsigned long*)buffer);
        if (flashoffset != 0){
            // Get kernel from armsys
            // If flashoffset == 0x80000, get logo picture from armsys
            if (flashoffset != LOGO_OFFSET)
                flashoffset = KERNEL_OFFSET;
        }

        // jump to run program if u don't need write flash
        if (IsWriteFlash) {
            SerialOutputString("Write Image to Nandflash\n");
            if(Do_WriteKernel(buffer, flashoffset, packsize) == -1)
                    SerialOutputString("Write Image Failed!\n");
                  else
                    SerialOutputString("Write Image Succeeded!\n");
        }
        // image is already in the dram
        if (flashoffset == 0){//run eldr or test
              if (IsWriteFlash)WatchdogReset();
            else {
                Boot();
            }
        }
//        if (flashoffset == LOGO_OFFSET){//run eldr or test
//             msleep(5000);
//             while (1) printk("*");
//        }
        else {
            //BootKernel(0, 0);
        }
    }
}
